package com.shortLinked.admin.common.biz.user;

import com.alibaba.fastjson2.JSON;
import com.google.common.collect.Lists;
import com.shortLinked.admin.common.config.UserFlowRiskControlConfiguration;
import com.shortLinked.admin.common.convention.errorcode.BaseErrorCode;
import com.shortLinked.admin.common.convention.exception.ClientException;
import com.shortLinked.admin.common.convention.result.Results;
import jakarta.servlet.*;
import jakarta.servlet.http.HttpServletResponse;
import lombok.RequiredArgsConstructor;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.scripting.support.ResourceScriptSource;

import java.io.IOException;
import java.io.PrintWriter;
import java.util.Optional;

import static com.shortLinked.admin.common.biz.user.ColoredOutput.RED;
import static com.shortLinked.admin.common.biz.user.ColoredOutput.RESET;

/**
 * 用户操作流量风控过滤器 - 普通过滤器
 */
@Slf4j
@RequiredArgsConstructor
public class UserFlowRiskControlFilter implements Filter {

    private final StringRedisTemplate stringRedisTemplate;

    private final UserFlowRiskControlConfiguration userFlowRiskControlConfiguration;
//这是一个配置类，用来保存流量风控的配置参数，如时间窗口和最大访问次数。
    private static final String USER_FLOW_RISK_CONTROL_LUA_SCRIPT_PATH = "lua/user_flow_risk_control.lua";

    @SneakyThrows
    @Override
    public void doFilter(ServletRequest servletRequest, ServletResponse servletResponse, FilterChain filterChain) throws IOException, ServletException {
        System.out.println(RED+"这里是:doFilter2"+RESET);
        // 配置 lua 脚本
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();
        //这是 Redis 执行 Lua 脚本的一个类，用于包装 Lua 脚本并定义其返回类型。
        redisScript.setScriptSource(new ResourceScriptSource(new ClassPathResource("lua/user_flow_risk_control.lua")));
        //指定脚本来源，这里是从 resources/lua/user_flow_risk_control.lua 加载 Lua 脚本。
        redisScript.setResultType(Long.class);
        // 根据某个用户信息去执行 lua 脚本
        String username = Optional.ofNullable(UserContext.getUsername()).orElse("other");
        //从上下文中获取当前用户的用户名，默认值是 "other"，这通常用于表示匿名用户或未登录用户
        Long result = null;
        try {
            result = stringRedisTemplate.execute(redisScript, Lists.newArrayList(username), userFlowRiskControlConfiguration.getTimeWindow());
            //stringRedisTemplate.execute：执行 Lua 脚本。传入参数包括
            //redisScript：要执行的脚本。
            //Lists.newArrayList(username)：传给 Lua 脚本的参数列表，这里传入的是当前用户名。
            //userFlowRiskControlConfiguration.getTimeWindow()：流量控制的时间窗口（例如 1 分钟）
        } catch (Throwable ex) {
            log.error("执行用户请求流量限制Lua脚本出错", ex);
            returnJson((HttpServletResponse) servletResponse, JSON.toJSONString(Results.failure(new ClientException(BaseErrorCode.FLOW_LIMIT_ERROR))));
            //"A000300", "当前系统请求频繁，请稍后再试"
        }
        //如果执行过程中出现异常，会捕获异常并记录错误日志，同时返回错误信息给客户端。
        if (result == null || result > userFlowRiskControlConfiguration.getMaxAccessCount()) {
            //一秒内最多访问十次
            returnJson((HttpServletResponse) servletResponse, JSON.toJSONString(Results.failure(new ClientException(BaseErrorCode.FLOW_LIMIT_ERROR))));
        }
        //result 是 Lua 脚本返回的请求计数。如果返回值大于配置的最大请求次数 maxAccessCount，则认为超出了流量限制。
        //如果超出了流量限制，调用 returnJson 方法返回一个流量限制错误信息给客户端。
        filterChain.doFilter(servletRequest, servletResponse);
        //如果流量限制检查通过，调用 filterChain.doFilter 继续执行请求链，允许请求继续向下传递。

    }

    // 将错误异常信息抛出去
    private void returnJson(HttpServletResponse response, String json) throws IOException {
        System.out.println(RED+"这里是:returnJson"+RESET);
        response.setCharacterEncoding("utf-8");
        response.setContentType("text/html; charset=utf-8");
        try (PrintWriter writer = response.getWriter()) {
            writer.println(json);
        }
    }
    //这个过滤器的核心功能是根据用户的请求对其进行流量控制，防止同一用户在短时间内发送过多请求。
    //它通过 Redis 和 Lua 脚本实现了高效的请求计数与流量限制，利用 Redis 的原子操作避免了并发问题。
    //当用户请求次数超过了设定的限制时，过滤器会阻止请求继续传递，并返回一个流量限制错误的 JSON 响应。
}
